package wangjg.mda.lang;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import wangjg.commons.bean.BeanUtil;
import wangjg.mda.model.obj.Method;
import wangjg.mda.model.obj.Parameter;

public class CommentParser extends RegExParser {
	private static final String REG_LINE_COOMENT = "//(.*?)$";
	private static final String REG_BLOCK_COOMENT = "/\\*(\\*?)(.*?)\\*/";
	private static final String REG_DOC_PREFIX = "^\\s*\\*\\s*(\\S*?)$";
	
	protected static final String REG_IDENTIFIERS="[a-z_A-Z\\$]\\w*";
	
	//inner
	protected String sourceCode;
	private List<Integer> lineRecord = new ArrayList<Integer>();//记录每行的结束位置
	private Map<Integer,MyComment> commentMap = null;//end line:comment
	
	private static Map<String,Integer> commentLevel = new HashMap<String,Integer>();
	static{
		commentLevel.put("doc", 3);
		commentLevel.put("block", 2);
		commentLevel.put("line", 1);	
	}
	
	public void loadSource(String filePath, String charset){
		if(this.sourceCode!=null&&!this.sourceCode.isEmpty()){
			return ;
		}
		FileInputStream fis=null;
		try {
			fis = new FileInputStream(filePath);
			InputStreamReader reader = new InputStreamReader(fis, charset);
			BufferedReader br = new BufferedReader(reader);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while((line=br.readLine())!=null){
				sb.append(line);
				sb.append('\n');
				lineRecord.add(sb.length());
			}
			sourceCode = sb.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try{
				if(fis!=null){fis.close();}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	
	public Map<Integer, MyComment> getCommentMap() {
		if(commentMap!=null){
			return commentMap;
		}
		commentMap = new LinkedHashMap<Integer,MyComment>();
		this.getLineComments(sourceCode);
		this.getBlockComments(sourceCode);
		return commentMap;
	}
	
	public MyComment queryComment(int...lines){
		for(int lineNumber:lines){
			MyComment comment = this.commentMap.get(lineNumber);
			if(comment!=null){
				return comment;
			}
		}
		return null;
	}
	
	public MyComment queryComment2(int startPos, int endPos){
		int startLine = this.getLineNumber(startPos);
		int endLine = this.getLineNumber(endPos);
		MyComment result = null;
		for(int i = endLine; i>=startLine; i--){
			MyComment comment = this.getCommentMap().get(i);
			if(comment!=null){
				if(result==null){
					result = comment;
				}else{
					Integer level1 = commentLevel.get(comment.getType());
					Integer level2 = commentLevel.get(result.getType());
					if(level1==null){level1=0;}
					if(level2==null){level2=0;}
					if(level1>level2){
						result = comment;
					}
				}
			}
		}
		return result;
	}

	private void getLineComments(String sourceCode){
		Matcher matcher = this.getMatcher(sourceCode, REG_LINE_COOMENT);
		while(matcher.find()){
			int startPos = matcher.start();
			int endPos = matcher.end();
			int endLineNo = this.getLineNumber(endPos);
			String value=matcher.group(1);
			if(value!=null)value=value.trim();
			MyComment comment = new MyComment();
			comment.setBlockStartPos(startPos);
			comment.setBlockEndPos(endPos);
			comment.setType("line");
			_parseComment(comment, value);
			this.commentMap.put(endLineNo, comment);
		}
	}

	private void getBlockComments(String sourceCode){
		Matcher matcher = this.getMatcher(sourceCode, REG_BLOCK_COOMENT);
		while(matcher.find()){
			int startPos = matcher.start();
			int endPos = matcher.end();
			int lineNo = this.getLineNumber(endPos);
			String docFlag = matcher.group(1);
			String value = matcher.group(2);
			if(value!=null){
				value=value.trim();
			}
			MyComment comment = new MyComment();
			if("*".equals(docFlag)){
				comment.setType("doc");
			}else{
				comment.setType("block");
			}
			comment.setBlockStartPos(startPos);
			comment.setBlockEndPos(endPos);
			
			_parseComment(comment, value);
			this.commentMap.put(lineNo, comment);
		}
	}
	

	/**
	 * @author user1
	 * @author user2
	 * @category h y
	 * @category h y
	 * @ deprecated
	 * @exception e1 e2
	 * @exception e3
	 * @param keyword 关键字
	 * @param p2
	 * @return prolist
	 * @see see1
	 * @see see2 222
	 * @serialData sd1
	 * @serialData sd2
	 * @since s1
	 * @since s2
	 * @throws throw1 1
	 * @throws throw2 2
	 * {@code}
	 * {@value}
	 * {@link url label}
	 * {@link #allowSell(Long, Long)}
	 * {@link com.xxx.User#setCnName(String)}
	 * @ejb.create-method
	 * @ref xx.id
	 */
	private void _parseComment(MyComment comment, String text){
		final String REG_COMMENT_PARAM="(?:^|\\s)\\**\\s*@("+REG_IDENTIFIERS+")(?:\\s*(.*))?$";
		BufferedReader br = new BufferedReader(new StringReader(text));
		StringBuilder descBuffer=new StringBuilder();
		String line = null;
		try {
			while((line=br.readLine())!=null){
				line = line.trim();
				Matcher matcher = this.getMatcher(line, REG_COMMENT_PARAM);
				if(matcher.find()){
					String type = matcher.group(1);
					String value = matcher.group(2);
					MyDocParameter param = new MyDocParameter();
					param.setType(type);
					if(value!=null){
						value=value.trim();
						int blankPos = value.indexOf(' ');
						if(value.length()>0&&blankPos!=-1){
							String tag = value.substring(0,blankPos);
							if(checkPattern(REG_IDENTIFIERS, tag)){
								String val = value.substring(blankPos);
								param.setTag(tag);
								param.setValue(val);	
							}else{
								param.setValue(value);
							}
						}else{
							param.setValue(value);
						}
					}
					comment.getParams().add(param);
					if("line".equals(comment.getType())){
						String str=line.substring(0, matcher.start(1)-1);
						descBuffer.append(str.trim());
					}
				}else{
					if(line.length()>0){
						String str = line;
						if(str.startsWith("*")){
							str = str.substring(1);
						}
						if(comment.getParams().size()==0){
							if(descBuffer.length()!=0){
								descBuffer.append('\n');
							}
							descBuffer.append(str);
						}else{
							MyDocParameter param = comment.getParams().get(comment.getParams().size()-1);
							param.setValue(param.getValue()+'\n'+str.trim());
						}
					}
				}
			}
			if(descBuffer.length()>0){
				comment.setValue(descBuffer.toString().trim());	
			}
			
			// link
			comment.setValue(__parseLink(comment.getValue()));
			for(MyDocParameter param:comment.getParams()){
				param.setValue(__parseLink(param.getValue()));
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private String __parseLink(String text){
		if(text==null||text.indexOf("{@link")==-1){
			return text;
		}
		final String REG_COMMENT_LINK="\\{@link ([a-z_A-Z\\$](?:\\.?\\w)*)?(?:#("+REG_IDENTIFIERS+"))?(?:(\\()([^\\)\\}]*)(\\)))?(?: ([^\\)\\}]*))?\\}";
		
		StringBuilder sb = new StringBuilder();
		int textPos = 0;
		Matcher matcher = this.getMatcher(text, REG_COMMENT_LINK);
		while(matcher.find()){
			MyCommentLink link = this.__getMyCommentLink(matcher);
			sb.append(text.substring(textPos, matcher.start()));
			
			String label = link.getLabel();
			if(label==null){
				String type = link.getType()==null?"":link.getType();
				String mn = link.getMemberName()==null?"":"."+link.getMemberName();
				label = type+mn;
				if(link.getParams()!=null){
					label = label+'('+link.getParams()+')';
				}
			}
			
			sb.append(label);
			textPos = matcher.end();
		}
		sb.append(text.substring(textPos));
		return sb.toString();
	}
	
	protected MyCommentLink __getMyCommentLink(Matcher matcher){
		MyCommentLink link = new MyCommentLink();
		link.setType(matcher.group(1));
		link.setMemberName(matcher.group(2));
		if("(".equals(matcher.group(3))&&")".equals(matcher.group(5))){
			link.setMethod(true);
		}
		String params = matcher.group(4);
		if(params!=null){
			List<String> paraList = java.util.Arrays.asList(params.split(","));
			link.setParams(paraList);
		}
		String label = matcher.group(6);
		link.setLabel(label);
		return link;
	}

	protected int getLineNumber(int pos){
		for(int i=0;i<lineRecord.size();i++){
			if(lineRecord.get(i)>pos){
				return i+1;
			}
		}
		return -1;
	}

	public String getSourceCode() {
		return sourceCode;
	}
	

	public void setMethodCommentInfo(Method method, MyComment comment){
		if(comment==null){
			return ;
		}
		method.setDescription(comment.getValue());
		Map paramMap = BeanUtil.groupByProperty(comment.getParams(), "type");
		List<MyDocParameter> returnParameters = (List<MyDocParameter>)paramMap.get("return");
		if(returnParameters!=null && returnParameters.size()==1){
			method.setReturnDescription(returnParameters.get(0).getValue());	
		}
		Map<String,String> throwDesMap = new HashMap<String,String>();
		method.setThrowDesMap(throwDesMap);
		List<MyDocParameter> throwsParameters = (List<MyDocParameter>)paramMap.get("throws");
		if(throwsParameters!=null){
			Map<String,MyDocParameter> docParamMap = BeanUtil.groupByUniqueProperty(throwsParameters, "tag");
			for(String e:method.getThrowz()){
				MyDocParameter eDocParam = docParamMap.get(e);
				if(eDocParam!=null && eDocParam.getValue()!=null)
				throwDesMap.put(e, eDocParam.getValue());
			}
		}
		List<MyDocParameter> paramDocParamList = (List<MyDocParameter>)paramMap.get("param");
		if(paramDocParamList!=null && paramDocParamList.size()>0){
			Iterator<MyDocParameter> it = paramDocParamList.iterator();
			while(it.hasNext()){
				MyDocParameter param = it.next();
				if(param.getTag()==null||param.getValue()==null){
					it.remove();
				}
			}
			Map<String,MyDocParameter> docParamMap = BeanUtil.groupByUniqueProperty(paramDocParamList, "tag");
			for(Parameter param : method.getParameters()){
				MyDocParameter myDocParam = docParamMap.get(param.getName());
				if(myDocParam!=null && myDocParam.getValue()!=null){
					param.setDescription(myDocParam.getValue().trim());
				}
			}
		}
	}
}
